<?php
/** Abstract class for array types
*   The ArrayType does not support any properties other than its values,
*       so accessing elements using Array or Object notation is equivalent
*
*/
abstract class Yz_Array_Abstract
    extends Yz_Class
    implements ArrayAccess,Countable,Yz_Array_Interface
{

    /******************************
    ***     Public methods      ***
    *******************************/

    /**
     *   @param mixed value
     */
    public function __construct( $value=null )
    {
        if ( null !== $value )
            $this->__dict__ = static::test( $value );
    } // __construct


    /** a chainable constructor
     *  @param array-type value
     *  @return chainable
     */
    public static function fromArray($array)
    {
        $res = new static;
        $res->__dict__ = static::test( $array );
        return $res;
    }



    /** toString()
    *
    *   @return string
    */
    public function __toString()
    {
        $cl = $this->_class_();
        $count = $this->count();
        return "{$cl}[{$count}]";
    } // toString


    /** valueOf():
    *   return the value as array if possible
    *   Supports PHP native arrays, stdclass, ArrayObject, Traversable, and of course Yz_Array_Interface
    *
    *   @param mixed $value
    *   @return null|array value
    */
    public static function valueOf( $value )
    {
        switch ( true ) {
            case is_array($value):
                $val = $value;
                break;
            case $value instanceof Yz_Class:
                $val = $value->toArray();
                break;
            case $value instanceof stdclass:
                // convert to an array
                $val = get_object_vars($value);
                break;

            case $value instanceof ArrayObject:
                $val = $value->getArrayCopy();
                break;
            case $value instanceof Traversable:
                $val = iterator_to_array($value, true);
                break;

            default:
                $val = null;
        }
        return $val;
    } // valueOf



    /** test whether a value is the same object as this
    *
    * @param mixed value
    * @return bool true if value is identical to $this
    */
    public function equals( $value )
    {
        return ( $value === $this );
    } // equals



    /***************************
     ***** other methods *******
     ***************************/

    /**
    *   Apply a callback to the internal value of a Yz_Class instance
    *
    *   Example using PHP53 lambda function feature:
    *       <code>
    *       $x = new Yz_String;
    *       $x->apply( function($value) { return strtolower($value); } );
    *       </code>
    *   @param callback: <type> callback(<type>)
    *   @return this
    *   @throws TypeError if $callback is not a valid callback
    */
    public function map( $callback )
    {
        if( ! is_callable( $callback ) )
            throw TypeError::_new_();

        $map = new $this;
        $mapvalue =& $map->__dict__;
        if ( $this->__dict__ )

            // can't use array_map because it doesn't preserve keys
            foreach( $this->__dict__ as $index => $value )
                $mapvalue[$index] = call_user_func( $callback, $value );

        return $map;
    } // map


    /**
    *   Apply callback to all elements of the array:
    *       the old values will be replaced by those returned by the callback
    *   @param callback $callback: mixed callback( index, value )
    *   @return chainable
    *   @throw TypeError if callback is not callable
    */
    public function apply( $callback )
    {
        debug();

        if ( ! is_callable( $callback ) )
            throw TypeError::_new_();

        if ( $this->__dict__ )

            // can't use array_map because it doesn't preserve keys
            foreach( $this->__dict__ as $index => &$value )
                $value = call_user_func( $callback, $index, $value );

        return $this;
    } // apply


    /** filter values using a callback
    *       Remove all elements which fail the callback (callback returns false)
    *   @param callback $callback: bool callback( value ), or null for the default callback
    *       Default callback will remove any elements with a value of null,false,'' (empty string)
    *   @return chainable
    *   @throw TypeError if callback is not callable
    */
    public function filter( $callback=null )
    {
        debug();
        if (null === $callback)
            $callback = array($this, 'filterCallback');
        else if ( ! is_callable( $callback ) )
            throw TypeError::_new_();

        if ( $this->__dict__ )
            $this->__dict__ = array_filter($this->__dict__, $callback);
        return $this;
    } // filter


    /** join the values with a string
     *
     *  @param string $join_str, default is comma
     *  @return string
    *   @todo what about non-string values in __dict__?
     */
    public function join( $str="," )
    {
        debug();
        $str = Yz_String::str( $str );
        return join( $str, $this->__dict__ );
    } // join

    /** get an array containing some of the elements of the original array
     *
     *  @param int $from
     *  @param int|null $count
     *  @return self/Yz_Array_Abstract
     *  @todo check param types
     */
    public function slice( $from, $count=null )
    {
        debug();

        return $this->_slice($from, $count, false);
    } // slice


    /** get an array containing all of the elements of the input arrays
     *
     *  @param Yz_Array_Interface
     *  @return self/Yz_Array_Abstract
     *  @todo check param types
     */
    public function union( $array )
    {
        debug();

        $array = $this::test($array);

        $this->__dict__ = $this->__dict__ + $array;
        return $this;
    } // union



    /**********************************
     ********   Protected methods *****
     *********************************/


    /**
     *  the real implementation of slice(), with choice whether to preserve keys
     *  @param int $from
     *  @param int|null $count
     *  @param bool $keys
     *  @return self/Yz_Array_Abstract
     *  @todo check param types
     */
    protected function _slice( $from, $count, $keys )
    {
        return new $this( array_slice($this->__dict__, $from, $count, $keys) );
    }

    public function filterCallback($value)
    {
        return ! (null === $value or false === $value or "" === $value);
    }

}
